<?php


require_once dirname(__FILE__).'/../PEAR/Net/Socket.php';

/**
 * SDO 专用socket 底层操作
 * 根据协议进行打包，解包操作
 * @category   SDO
 * @package    Relation
 * @author     ggg <wangweijun01@snda.com> 2010-3-15 17:23
 * @copyright  SDO OP
 * @license    http://www.opensource.org/licenses/bsd-license.php  BSD License
 * @link       http://www.sdo.com/
 * @since      File available since Release 1.0.0
 */

class SDOSocket {

    
    const EC_SOCKET_CREATE      = -1;
    const EC_SOCKET_CONNECT     = -2;
    const EC_SOCKET_SELECT      = -3;
    const EC_SOCKET_TIMEOUT     = -4;
    const EC_SOCKET_READ        = -5;
    const EC_SOCKET_WRITE       = -6;
    const EC_SOCKET_PACK        = -7;
    
   
    static $errorMap = array(
        self::EC_SOCKET_CREATE      => "Socket Create Error",  
        self::EC_SOCKET_CONNECT     => "Socket Connect Error",
        self::EC_SOCKET_SELECT      => "Socket Select Error",
        self::EC_SOCKET_TIMEOUT     => "Socket Timeout",
        self::EC_SOCKET_READ        => "Socket Recv Error",
        self::EC_SOCKET_WRITE       => "Socket Send Error",
        self::EC_SOCKET_PACK        => "Socket Pack Error"
    );
    
    
 	/**
	 * 保存连接的服务器
	 */
	 private static $_serverArray	=  array(
		 //好友关系
		array('host' => '114.80.135.7','port' => '1234')
		);


	
 	/**
	 * 采用http方式请求的url
	 */
	 private static $_httpRestUrl	=  'http://114.80.135.7:8080/';

 	/**
	 * 对要发送的数据进行打包
	 * @param String apiName API名称
	 * @param String apiParam API相关参数
	 * @return  String 返回打包好后的数据
	 */
	static public function pack($apiName,$apiParam)
	{
		/*
				包的总大小 4字节
				uVersion 2字节			uProtocolType 2字节	  0x0000 json 格式协议 0x0001 xml 格式协议
				命令uCmd4字节		0x01000001 心跳包 0x01000002 心跳应答 0x02000001 请求服务 0x02000002 服务应答
				uClientID				应用端标识（盛大SDO统一分配的ID） 4字节
				uClientTransID		应用端发起的事务ID
				uClientUserID			(uClientUserID)	指定用户的数字账号 8字节

				//以上包头总计28字节

				uAPINameLength		4 字节整型，需要调用的API 名称字符串字节长度
				strAPIName			uAPINameLength 长度的API 名称字符串，

				uParamListLength：必须，4 字节整型，API 对应的参数列表串字节长度。
				strParamList：			可选，uParamListLength 长度的参数列表串。具体参数列表详见各模块接口文档
		*/

		//-------------- 包头除去uSize部分 ---
		

		//2字节整型，协议版本号，目前版本号1
		$uVersion	= 1;
		//2字节整型，协议格式    0x0000  json格式协议 0x0001 xml格式协议
        $uProtocolType  = 0x0000;
        //4字节整型， 命令 0x01000001 心跳包 0x01000002 心跳应答 0x02000001 请求服务 0x02000002 服务应答
        $uCmd   = 0x02000001;
        //4字节整型， 应用端标识（盛大SDO统一分配的ID）
        $uClientID  = 0x01000001;
        //4字节整型， 应用端发起的事务ID
        $uClientTransID = 0x01000001;
        //8字节整型， 应用端发起请求的用户ID，服务器回填
        $uClientUserIDLow  = 0x01000001;
        $uClientUserIDHigh  = 0x01000001;

        $pack_head  = pack('nnNNNNN',$uVersion,$uProtocolType,$uCmd,$uClientID,$uClientTransID,
                            $uClientUserIDLow,$uClientUserIDHigh);

		//-------------- 包体 ---
		$pack_body	= '';
        
        //4字节整型，需要调用的API名称字符串字节长度
        $uAPINameLength = (0x00000000 | strlen($apiName));
		$pack_body.= pack("N",$uAPINameLength);

		//uAPINameLength 长度的API名称字符串
		$strAPIName	= $apiName;
		$pack_body.=$strAPIName;

		//4字节整型，API对应的参数列表串字节长度。
		$uParamListLength	= (0x00000000 | strlen($apiParam));
		$pack_body.= pack("N",$uParamListLength);

		//uParamListLength长度的参数列表串
		$strParamList	= $apiParam;
		$pack_body.=$strParamList;
	   
		//拼上总包长度,返回结果
		$uSize = 0x00000000;
		$tmp_pack_str	= $pack_head.$pack_body;
		$ret_str	= pack("N",(0x00000000 | (strlen($tmp_pack_str) + 4))).$tmp_pack_str;
		return $ret_str;
	}

 	/**
	 * 对收到的的数据进行解包
	 * @param String 
	 * @return String
	 */
	static public function unPack($paramArray)
	{
	    $unpackedArray = unpack("NuSize/nuVersion/nuProtocolType/".
	                            "NuCmd/NuClientID/NuClientTransIDD/NuClientUserIDLow/".
	                            "NuClientUserIDHigh/NuAnswerLength/a*Answer",
	                              $paramArray);
	    
		return $unpackedArray['Answer'];
	}

 	/**
	 * http 方式请求
	 * @param String $apiName API名称
	 * @param Array $apiParam API相关参数
	 * @return  
	 */
	static public function httpRequest($apiName,$apiParam='')
	{

//print_r($apiParam);					
		if(!is_array($apiParam))
			$apiParam	= array();
		$tmp_param	= http_build_query($apiParam);
	
		//转成按 / 分割
		$apiName	= strtr($apiName, '.', "/");
		$tmp_url	= self::$_httpRestUrl . $apiName.'?'.$tmp_param;
//echo $tmp_url;		
		//需要返回json格式
		$option	= array(CURLOPT_HTTPHEADER => Array('application/json;charset=UTF-8'));
		$result_str	= self::curlRequest($tmp_url,'',$option);
		return json_decode($result_str,true);

	}


 	/**
	 * curl 方式请求
	 * @param String $url 请求网址
	 * @param String $postString 要post的参数 格式如  xxx=1&yyy=2
	 * @param Array $option 参数 如 array(CURLOPT_REFERER=>'http://www.51.com/',CURLOPT_HTTPHEADER=>array('Host: client.sdo.com'))
	 *  
	 * @return  
	 */
	static public function curlRequest($url,$postString='',$option='')
	{
		$ch = curl_init();
		curl_setopt($ch,CURLOPT_URL,$url);
		curl_setopt($ch,CURLOPT_POSTFIELDS,$postString);
		curl_setopt($ch,CURLOPT_RETURNTRANSFER,true);
		//curl_setopt($ch,CURLOPT_USERAGENT,$_SERVER['HTTP_USER_AGENT']);

		//设置其他参数
		if(!empty($option) && is_array($option))
		foreach($option as $key => $val)
		{
			curl_setopt($ch, $key,$val);
		}

		$data = curl_exec($ch);
		//$info = curl_getinfo($ch);
		curl_close($ch);
		return $data;
	}

 	/**
	 * 发送数据包
	 * @param String apiName API名称
	 * @param Array $apiParam API相关参数
	 * @return  
	 */
	static public function send($apiName,$apiParam)
	{
		if(!is_array($apiParam))
		{
			$apiParam	= array();
		}
		
		
		$apiParam	= http_build_query($apiParam);
		//数据打包
		$pack_str	= self::pack($apiName,$apiParam);

		//发送数据包
		$tmp_array	= self::$_serverArray;
		$result_str	= self::tpcRequest($pack_str,$tmp_array[0]);
        
		if (!is_string($result_str))
		{
		    $errorCode = $result_str;
		    return array(
		      'code' => 400,
		      'msg' => implode(':', array($errorCode." ", " ".self::$errorMap[$errorCode]))
		    );
		}
		else
		{
		    //数据解包，返回
            $result = json_decode(self::unPack($result_str),true);
            return $result; 
		}
	}
	
	/*
	 * 检查返回是否正常
	 * @param $result 调用send后的结果
	 * @return boolean
	 */
	static function isError($result)
	{
	    if (isset($result['code']))
	    {
            if ($result['code'] == 0)
            {
	           return false;
            }
	    }
	    return true;
	}

	/**
	 * @param String apiName API名称
	 * @param String apiParam API相关参数
	 * @param Boolen isNoBlock 是否要阻塞方式
	 * @param Boolen isRead 是否要接收服务器返回
	 * 发起 tcp 请求
	 * @return  
	 * isRead 
	 */
	static public function tpcRequest($comm,$serverInfo,$isNoBlock=false,$isRead=true)
	{
	    $sock = new Net_Socket();

        // open connection
        $result = $sock->connect($serverInfo['host'], $serverInfo['port'], false, 0.5);
        
        if($sock->isError($result))
        {
            return self::EC_SOCKET_CONNECT;
        }
       
        $sock->setTimeout(1,0);
        
        //发送数据包
        $result = $sock->write($comm);
        
	    if($sock->isError($result) )
        {
            $sock->disconnect();
            return self::EC_SOCKET_WRITE;
        }
		
	    
		//不理服务器返回
		if (!$isRead)
		{
			socket_close($sock);
			return "";
		}
		
		
		
	    //获取应答包长度
        $uSize = $sock->read(4);
        if($sock->isError($uSize) || empty($uSize))
        {
            $sock->disconnect();
            return self::EC_SOCKET_READ;
        }
        
        $unpackedSize = unpack("N",$uSize);
        $len = $unpackedSize[1] - 4;
        if ($len <= 0)
        {
            $sock->disconnect();
            return self::EC_SOCKET_PACK;
        }
        
        
        //获取应答包体
        $Body = $sock->read($len);
        if($sock->isError($Body) || empty($Body))
        {
            $sock->disconnect();
            return self::EC_SOCKET_READ;
        }
		
		$sock->disconnect();
        return $uSize.$Body;
	}

    

	
	
	/**
	 * xml格式转成php 数组格式
	 * @param String $xmlStr
	 * @return  Array
	 */
	public function xml2Array($xmlStr)
	{	
		return array();
	}


}